<?php
// +----------------------------------------------------------------------
// | Author: wyd 2025/11/3
// | DESC: 
// +----------------------------------------------------------------------

declare (strict_types=1);


namespace My\core\pool;

use Swoole\Coroutine\Channel;
use Swoole\Timer;

abstract class DbPool
{
    private $conns;

    //连接数量
    //在PHP中，如果属性有类型声明，那么必须在读取之前初始化，否则会抛出错误
    private int $count = 0;

    //过期时间
    private int $idleTime = 10;  //最大连接空闲时间

    abstract public function db();


    public function __construct(private int $min, private int $max)
    {
        $this->conns = new Channel($max);
    }


    public function init()
    {
        for ($i = 0; $i < $this->min; $i++) {
            $this->addPool();
        }

        /**定时清除空闲链接数**/
        Timer::tick(5000, function () {
            $this->cleanPool();
        });
    }


    /**
     * @return mixed
     * @throws \Exception
     */
    public function getConnection(): mixed
    {
        //1、链接池不为空直接取
        if (!$this->conns->isEmpty()) {
            $dbObj = $this->conns->pop();
            $dbObj->usedTime = time(); //重置时间
            return $dbObj->db;
        }


        //2、链接池为空
        if ($this->count < $this->max) {
            //2.1.小于最大链接数
            $this->addPool();
            $dbObj = $this->conns->pop();
            $dbObj->usedTime = time(); //重置时间
            return $dbObj->db;
        } else {
            //2.2：连接池为空且已达最大连接数
            $dbObj = $this->conns->pop(5.0);
            if ($dbObj === false) {
                throw new \RuntimeException('获取数据库连接超时');
            }
            $dbObj->usedTime = time();
            return $dbObj->db;
        }
    }


    /**
     * @return void
     * @throws \Exception
     */
    public function addPool()
    {
        try {
            //先++，new的时候会导致协程
            $this->count++;
            $db = $this->db();

            /**只对新加的池，有空闲时间（超过最小连接数*/
            $dbObj = new \stdClass();
            $dbObj->usedTime = time();
            $dbObj->db = $db;

            //$this->conns->push($db);
            $this->conns->push($dbObj);
        } catch (\Exception $e) {
            $this->count--;
            throw  $e;
        }
    }


    public function close($conn)
    {
        $dbObj = new \stdClass();
        $dbObj->db = $conn;
        $dbObj->usedTime = time();
        $this->conns->push($dbObj);
    }


    public function cleanPool()
    {
        // 如果连接数小于等于最小连接数或小于最大连接数的60%，不清理
        if ($this->conns->length() <= $this->min || $this->conns->length() < intval($this->max * 0.6)) {
            return;
        }

        echo '开始执行清理' . PHP_EOL;

        $tempConns = []; // 临时存放需要保留的连接
        $cleanedCount = 0; // 清理的连接计数

        // 遍历当前连接池中的所有连接
        while (!$this->conns->isEmpty()) {
            $dbObj = $this->conns->pop(0.1); // 添加短暂超时避免死锁
            if ($dbObj === false) {
                break; // 如果pop失败，退出循环
            }

            // 检查是否需要清理
            if ($this->count > $this->min && (time() - $dbObj->usedTime) > $this->idleTime) {
                // 清理这个连接
                $this->count--;
                $cleanedCount++;
                echo "清理了一个空闲连接，空闲时间: " . (time() - $dbObj->usedTime) . "秒" . PHP_EOL;
                // 这里可以添加实际关闭连接的逻辑
                // $this->closeConnection($dbObj->db);
            } else {
                // 保留这个连接
                $tempConns[] = $dbObj;
            }
        }

        // 将需要保留的连接重新放回连接池
        foreach ($tempConns as $dbObj) {
            $this->conns->push($dbObj);
        }

        echo "清理完成，共清理了 {$cleanedCount} 个连接" . PHP_EOL;
        echo $this->getConnNum() . PHP_EOL;
    }

    public function getConnNum()
    {
        return '当前链接数为' . $this->count;
    }
}